/*
 * Camera.cpp
 *
 *  Created on: 04.06.2011
 *      Author: Kirill Blinov
 *      Copyright: LGPL
 */

#include "Camera.h"
#include "../Constants.h"
#include <cmath>

CGameCamera::CGameCamera()  //TODO Remove hardcoded values
{
	vec3 Eye(40.0f, -5.0f, -5.0f);
	orienatation = glm::quat( 0.473755f, glm::vec3( 0.508040f, 0.526091f, -0.490588f ) );

	currentGameCamera.leftTopX = 0;
	currentGameCamera.leftTopX = 0;
	currentGameCamera.projectionMatrix = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);
	currentGameCamera.height = 1.0;
	currentGameCamera.width  = 1.0;

	CalculateInitParams( Eye, orienatation );
	CalculateClipPlanes( currentGameCamera.frustum, (currentGameCamera.viewMatrix * currentGameCamera.projectionMatrix) );
	currentGameCamera.sphere = CalculateClipSphere( Eye, 0.1f, 100.0f, 45.0f );
}

CGameCamera::CGameCamera( vec3 Eye, glm::quat Orientation ) : orienatation(Orientation)
{
	currentGameCamera.leftTopX = 0;
	currentGameCamera.leftTopX = 0;
	currentGameCamera.projectionMatrix = glm::perspective(45.0f, 4.0f / 3.0f, 0.1f, 100.0f);
	currentGameCamera.height = 1.0;
	currentGameCamera.width  = 1.0;

	CalculateInitParams( Eye, Orientation );
	CalculateClipPlanes( currentGameCamera.frustum, (currentGameCamera.viewMatrix * currentGameCamera.projectionMatrix) );
	currentGameCamera.sphere = CalculateClipSphere( Eye, 0.1f, 100.0f, 45.0f );
}

void
CGameCamera::Translate( const float dx, const float dy, const float dz )
{
	glm::vec3 Translation( dx, dy, dz );
	glm::mat4 tmpMatrix = glm::translate( glm::mat4(1.0f),
			                              Translation );

	translationMatrix *= tmpMatrix;
	currentGameCamera.viewMatrix *= tmpMatrix;
	currentGameCamera.sphere = currentGameCamera.sphere * tmpMatrix;

	for ( std::vector<IUnit*>::const_iterator BeginIt = bindedUnits.begin(),
		  FinishIt = bindedUnits.end();
		  BeginIt != FinishIt; ++BeginIt )
	{
		IUnit* CurrentUnit = *BeginIt.base();
		CurrentUnit->Translate( Translation );
	}

	CalculateClipPlanes( currentGameCamera.frustum, (currentGameCamera.viewMatrix * currentGameCamera.projectionMatrix) );
}

void
CGameCamera::RotateEyeAroundPoint( const vec3 Point, const float angelX, const float angelY, const float angelZ )
{
    //TODO make up correct transformations
	glm::quat tmpQuat = glm::angleAxis( 1.0f, angelX, angelY, angelZ );
	orienatation = orienatation * tmpQuat;

	currentGameCamera.viewMatrix = /* TmpTransMatr * */  glm::toMat4( orienatation )
	                               /* * glm::inverse(TmpTransMatr)*/ * translationMatrix;
	CalculateClipPlanes( currentGameCamera.frustum, (currentGameCamera.viewMatrix * currentGameCamera.projectionMatrix) );
}

void
CGameCamera::RotateCenterAroundEye( const float angelX, const float angelY, const float angelZ )
{
	glm::quat tmpQuat = glm::angleAxis( 1.0f, angelX, angelY, angelZ );
	orienatation = orienatation * tmpQuat;
    //fprintf( stderr, "orienatation: %f, %f, %f, %f\n", orienatation.x, orienatation.y, orienatation.z, orienatation.w );

	currentGameCamera.viewMatrix =  glm::toMat4( orienatation ) * translationMatrix;
	CalculateClipPlanes( currentGameCamera.frustum, (currentGameCamera.viewMatrix * currentGameCamera.projectionMatrix) );
}

/*
 *
 */
UINT
CGameCamera::BindUnit( IUnit *Unit )
{
	bindedUnits.push_back( Unit );
	return ( bindedUnits.size() - 1 );
}

void
CGameCamera::CalculateInitParams( vec3 Eye, glm::quat Orientation )
{
	translationMatrix = translate( mat4(1.0f), Eye );
	currentGameCamera.viewMatrix = glm::toMat4( Orientation ) * translationMatrix;
}

void
CGameCamera::CalculateClipPlanes( glm::vec4 *Frustum, const glm::mat4 &ViewProjMatrix )
{
   // ViewProjMatrix[5] - ViewProjMatrix[7]; //FIXME May be it's necessary?

    Frustum[LEFT_CLIP_PLANE].x = ViewProjMatrix[0].w + ViewProjMatrix[0].x;
    Frustum[LEFT_CLIP_PLANE].y = ViewProjMatrix[1].w + ViewProjMatrix[1].x;
    Frustum[LEFT_CLIP_PLANE].z = ViewProjMatrix[2].w + ViewProjMatrix[2].x;
    Frustum[LEFT_CLIP_PLANE].w = ViewProjMatrix[3].w + ViewProjMatrix[3].x;

    Frustum[RIGHT_CLIP_PLANE].x = ViewProjMatrix[0].w - ViewProjMatrix[0].x;
    Frustum[RIGHT_CLIP_PLANE].y = ViewProjMatrix[1].w - ViewProjMatrix[1].x;
    Frustum[RIGHT_CLIP_PLANE].z = ViewProjMatrix[2].w - ViewProjMatrix[2].x;
    Frustum[RIGHT_CLIP_PLANE].w = ViewProjMatrix[3].w - ViewProjMatrix[3].x;

    Frustum[BUTTON_CLIP_PLANE].x = ViewProjMatrix[0].y + ViewProjMatrix[0].w;
    Frustum[BUTTON_CLIP_PLANE].y = ViewProjMatrix[1].y + ViewProjMatrix[1].w;
    Frustum[BUTTON_CLIP_PLANE].z = ViewProjMatrix[2].y + ViewProjMatrix[2].w;
    Frustum[BUTTON_CLIP_PLANE].w = ViewProjMatrix[3].y + ViewProjMatrix[3].w;

    Frustum[TOP_CLIP_PLANE].x = ViewProjMatrix[0].w - ViewProjMatrix[0].y;
    Frustum[TOP_CLIP_PLANE].y = ViewProjMatrix[1].w - ViewProjMatrix[1].y;
	Frustum[TOP_CLIP_PLANE].z = ViewProjMatrix[2].w - ViewProjMatrix[2].y;
	Frustum[TOP_CLIP_PLANE].w = ViewProjMatrix[3].w - ViewProjMatrix[3].y;

	Frustum[FAR_CLIP_PLANE].x = ViewProjMatrix[0].z;
	Frustum[FAR_CLIP_PLANE].y = ViewProjMatrix[1].z;
	Frustum[FAR_CLIP_PLANE].w = ViewProjMatrix[2].z;

	Frustum[NEAR_CLIP_PLANE].x = ViewProjMatrix[0].w - ViewProjMatrix[0].z;
	Frustum[NEAR_CLIP_PLANE].y = ViewProjMatrix[1].w - ViewProjMatrix[1].z;
	Frustum[NEAR_CLIP_PLANE].z = ViewProjMatrix[2].w - ViewProjMatrix[2].z;
	Frustum[NEAR_CLIP_PLANE].w = ViewProjMatrix[3].w - ViewProjMatrix[3].z;
}

glm::vec4
CGameCamera::CalculateClipSphere( const glm::vec3 &LookAt, const float ZNear, const float ZFar,const float Fovy )
{
	float Length = ZFar-ZNear;
	float Height = Length * tan( Fovy / 2 );
	glm::vec3 SphereCenter( 0.0f, 0.0f, ZNear - (Length / 2) + LookAt.z );
	glm::vec3 FarCorner( Height, Height, ( Length - (SphereCenter.z / 2) ) );

	glm::vec4 Result( SphereCenter, FarCorner.length() );

	return Result;
}
